/*
 * cache.c
 *
 *  Created on: May 23, 2012
 *      Author: fumin
 */

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include "lru_cache.h"
#include "../destor.h"

extern int check_container_in_list(struct container *con, int64_t cid);

/*
 * The container read cache.
 */
struct lruCache *new_lru_cache(int size, void (*free_elem)(void *),
							   int (*hit_elem)(void *elem, void *user_data))
{
	struct lruCache *c = (struct lruCache *)malloc(sizeof(struct lruCache));

	c->elem_queue = NULL;

	c->max_size = size;
	c->size = 0;
	c->hit_count = 0;
	c->miss_count = 0;

	c->free_elem = free_elem;
	c->hit_elem = hit_elem;
	c->second_hit_elem = hit_elem;

	return c;
}

struct lruCache *new_lru_cache_two_hitfun(int size, void (*free_elem)(void *),
										  int (*first_hit_elem)(void *elem, void *user_data),
										  int (*second_hit_elem)(void *elem, void *user_data))
{
	struct lruCache *c = (struct lruCache *)malloc(sizeof(struct lruCache));

	c->elem_queue = NULL;

	c->max_size = size;
	c->size = 0;
	c->hit_count = 0;
	c->miss_count = 0;

	c->free_elem = free_elem;
	c->hit_elem = first_hit_elem;
	c->second_hit_elem = second_hit_elem;

	return c;
}

void free_lru_cache(struct lruCache *c)
{
	g_list_free_full(c->elem_queue, c->free_elem);
	free(c);
}

/* find a item in cache matching the condition */
void *lru_cache_lookup(struct lruCache *c, void *user_data)
{
	GList *elem = g_list_first(c->elem_queue);
	while (elem)
	{
		if (c->hit_elem(elem->data, user_data))
			break;
		elem = g_list_next(elem);
	}
	if (elem)
	{
		c->elem_queue = g_list_remove_link(c->elem_queue, elem);
		c->elem_queue = g_list_concat(elem, c->elem_queue);
		c->hit_count++;
		return elem->data;
	}
	else
	{
		c->miss_count++;
		return NULL;
	}
}

void *lru_cache_lookup_two_hitfun(struct lruCache *c, void *user_data, int op)
{
	if (op == 1)
	{
		GList *elem = g_list_first(c->elem_queue);
		while (elem)
		{
			if (c->second_hit_elem(elem->data, user_data))
				break;
			elem = g_list_next(elem);
		}

		if (elem)
		{
			return elem->data;
		}
		else
		{
			c->miss_count++;
			return NULL;
		}
	}
	else if (op == 0)
		return lru_cache_lookup(c, user_data);
	else
		return nolru_cache_lookup(c, user_data);
}

void *nolru_cache_lookup(struct lruCache *c, void *user_data)
{
	GList *elem = g_list_first(c->elem_queue);
	while (elem)
	{
		if (c->hit_elem(elem->data, user_data))
			break;
		elem = g_list_next(elem);
	}
	if (elem)
	{
		return elem->data;
	}
	else
	{
		return NULL;
	}
}

void *lru_cache_lookup_lcs_two(struct lruCache *c, void *user_data1, void *user_data2)
{
	GList *elem = g_list_first(c->elem_queue);
	while (elem)
	{
		if (c->hit_elem(elem->data, user_data1) || c->hit_elem(elem->data, user_data2))
			return elem->data;
		elem = g_list_next(elem);
	}
	return NULL;
}

void *lru_cache_lookup_lcs_three(struct lruCache *c, void *user_data1, void *user_data2, void *user_data3)
{
	GList *elem = g_list_first(c->elem_queue);
	while (elem)
	{
		if (c->hit_elem(elem->data, user_data1) ||
			c->hit_elem(elem->data, user_data2) ||
			c->hit_elem(elem->data, user_data3))
			return elem->data;
		elem = g_list_next(elem);
	}
	return NULL;
}

int32_t check_restore_container(struct lruCache *c, void *user_data, int64_t cid)
{
	int32_t val = 0;
	GList *elem = g_list_first(c->elem_queue);

	while (elem)
	{
		if (check_container_in_list(elem->data, cid))
		{
			c->elem_queue = g_list_remove_link(c->elem_queue, elem);
			c->elem_queue = g_list_concat(elem, c->elem_queue);
			val = 1;
			break;
		}
		elem = g_list_next(elem);
	}
	return val;
}

void *opt_lru_cache_lookup(struct lruCache *c, void *user_data, int64_t cid)
{
	GList *elem = g_list_first(c->elem_queue);

	elem = g_list_first(c->elem_queue);
	while (elem)
	{
		if (c->hit_elem(elem->data, user_data))
			break;
		elem = g_list_next(elem);
	}
	if (elem)
	{
		c->elem_queue = g_list_remove_link(c->elem_queue, elem);
		c->elem_queue = g_list_concat(elem, c->elem_queue);
		c->hit_count++;
		return elem->data;
	}
	else
	{
		c->miss_count++;
		return NULL;
	}
}

void *lru_cache_lookup_without_update(struct lruCache *c, void *user_data)
{
	GList *elem = g_list_first(c->elem_queue);
	while (elem)
	{
		if (c->hit_elem(elem->data, user_data))
			break;
		elem = g_list_next(elem);
	}
	if (elem)
	{
		return elem->data;
	}
	else
	{
		return NULL;
	}
}
/*
 * Hit an existing elem for simulating an insertion of it.
 */
void *lru_cache_hits(struct lruCache *c, void *user_data,
					 int (*hit)(void *elem, void *user_data))
{
	GList *elem = g_list_first(c->elem_queue);
	while (elem)
	{
		if (hit(elem->data, user_data))
			break;
		elem = g_list_next(elem);
	}
	if (elem)
	{
		c->elem_queue = g_list_remove_link(c->elem_queue, elem);
		c->elem_queue = g_list_concat(elem, c->elem_queue);
		return elem->data;
	}
	else
	{
		return NULL;
	}
}

/*
 * We know that the data does not exist!
 */
void lru_cache_insert(struct lruCache *c, void *data,
					  void (*func)(void *, void *), void *user_data)
{
	void *victim = 0;
	if (c->max_size > 0 && c->size == c->max_size)
	{
		GList *last = g_list_last(c->elem_queue);
		c->elem_queue = g_list_remove_link(c->elem_queue, last);
		victim = last->data;
		g_list_free_1(last);
		c->size--;
	}

	c->elem_queue = g_list_prepend(c->elem_queue, data);
	c->size++;
	if (victim)
	{
		if (func)
			func(victim, user_data);
		c->free_elem(victim);
	}
}

void lru_cache_kicks(struct lruCache *c, void *user_data,
					 int (*func)(void *elem, void *user_data))
{
	GList *elem = g_list_last(c->elem_queue);
	while (elem)
	{
		if (func(elem->data, user_data))
			break;
		elem = g_list_previous(elem);
	}
	if (elem)
	{
		c->elem_queue = g_list_remove_link(c->elem_queue, elem);
		c->free_elem(elem->data);
		g_list_free_1(elem);
		c->size--;
	}
}

int lru_cache_is_full(struct lruCache *c)
{
	return c->size >= c->max_size ? 1 : 0;
}
